import axios from 'axios'
import { reactive, computed } from 'vue'
import { run } from '../thread/thread'
import { toFileChunk, toChunk } from '../utils/common'
import { toFriendlySize } from '../utils/time'
import { format } from 'date-fns'
import { toByteCount } from '../utils/byteUtils'

function uniqueId() {
    return [this.file.name, this.file.size, this.file.lastModified, this.chunks.length].join('_')
}
function createProgress(current, total) {
    return {
        current: current,
        total: total,
        percentage: Math.round((current / total) * 100)
    }
}
async function sign() {
    if (this.signatured && !this.signValue) {
        this.state = State.SIGNING
        try {
            this.signValue = await run('hashCode', 'hashCode', this.file, {
                onProgress: progress => this.onSignProgress?.call(this, progress)
            })
            this.state = State.SIGNED
            return this.signValue
        } catch (error) {
            this.state = State.ERROR
            this.error = error.message
        }
    }
}
async function exists() {
    this.state = State.EXISTING
    try {
        const response = await axios.get(this.existsUrl, {
            params: {
                name: this.uniqueId(),
                sign: await this.sign()
            }
        })
        const { isExist, data } = response.data
        this.chunks.forEach(it => {
            if (isExist) {
                it.state = State.SUCCESS
            } else {
                if (data.some(idx => idx == it.index)) {
                    it.state = State.SUCCESS
                }
            }
        })
        if (isExist) {
            this.state = State.SUCCESS
        } else {
            this.state = State.SIGNED
        }
        return isExist
    } catch (error) {
        this.state = State.ERROR
        this.error = error.message
    }
    return isExist
}
async function startUpload(batchSize) {
    if (await this.exists()) {
        return
    }
    let waitingList = this.chunks.filter(it => it.state == State.WAITING)
    let total = this.chunks.length
    this.completedCount = total - waitingList.length
    this.onProgress?.call(this, createProgress(this.completedCount, total))
    this.state = State.UPLOADING
    if (batchSize) {
        this.batchSize = batchSize
    }
    let batchs = toChunk(waitingList.length, Number(this.batchSize))
    for (const batch of batchs) {
        const uploading = waitingList.slice(batch.start, batch.end)
        const tasks = uploading.map(async it => {
            it.tokenSource = axios.CancelToken.source()
            it.state = State.UPLOADING
            let form = new FormData()
            form.append('chunk', it.chunk)
            form.append('index', it.index)
            form.append('total', it.total)
            form.append('unique_id', this.uniqueId())
            form.append('sign', this.signValue)
            form.append('name', this.name)
            try {
                it.startTime = Date.now()
                const response = await axios.post(this.uploadUrl, form, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    },
                    cancelToken: it.tokenSource.token,
                    onUploadProgress: event => {
                        it.progress.current = event.loaded
                        it.progress.total = event.total
                        it.progress.percentage = Math.round((event.loaded / event.total) * 100)
                        this.onChunkUploadProgress?.call(this, it, it.progress)
                    }
                })
                const { data, status, statusText } = response
                if (status == 200) {
                    it.state = State.SUCCESS
                    this.completedCount++
                    if (data.state == 'success') {
                        this.path = data.data
                        this.state = State.SUCCESS
                    }
                    this.onProgress?.call(this, createProgress(this.completedCount, total))
                } else {
                    it.state = State.ERROR
                    it.error = statusText
                    this.state = State.ERROR
                }
            } catch (error) {
                it.state = State.ERROR
                it.error = error.message
                this.state = State.ERROR
            } finally {
                it.endTime = Date.now()
                it.duration = it.endTime - it.startTime
            }
        })
        await Promise.all(tasks)
    }
    return this.path
}
async function cancel() {
    this.chunks.forEach(it => {
        if (it.state == State.UPLOADING) {
            it.tokenSource.cancel('取消上传')
        }
    })
}

export const State = Object.freeze({
    WAITING: 0,
    SIGNING: 1,
    SIGNED: 2,
    EXISTING: 3,
    EXISTED: 4,
    UPLOADING: 5,
    SUCCESS: 6,
    ERROR: 7
})

export function useUpload(options) {
    let {
        uploadUrl,
        file,
        name,
        chunkSizeUnit = '10M',
        existsUrl,
        signValue,
        batchSize = 10,
        onProgress,
        onSignProgress,
        onChunkUploadProgress
    } = options
    let lastModified = ''
    let size = 0
    if (file) {
        const time = new Date(file.lastModified)
        lastModified = format(time, 'yyyy-MM-dd HH:mm:ss')
        size = toFriendlySize(file.size)
        name = name || file.name
    }
    const uploadStat = reactive({
        existsUrl,
        uploadUrl,
        file,
        lastModified,
        size,
        name,
        chunkSizeUnit,
        chunkSize: 0,
        chunks: [],
        batchSize,
        batchs: [],
        state: State.WAITING,
        path: '',
        signValue,
        signatured: true,
        completedCount: 0,
        uniqueId,
        startUpload,
        exists,
        sign,
        cancel,
        onProgress,
        onSignProgress,
        onChunkUploadProgress,
        error: ''
    })
    uploadStat.chunkSize = computed(() => {
        return toByteCount(uploadStat.chunkSizeUnit)
    })
    uploadStat.chunks = computed(() => {
        let chunkSize = uploadStat.chunkSize
        if (chunkSize > 0) {
            return toFileChunk(uploadStat.file, chunkSize).map(it => ({
                chunk: it.chunk,
                index: it.index,
                total: it.total,
                state: State.WAITING,
                progress: {
                    current: 0,
                    total: 0,
                    percentage: 0
                }
            }))
        }
        return []
    })
    uploadStat.batchs = computed(() => {
        let batchSize = Number(uploadStat.batchSize)
        if (batchSize > 0) {
            return toChunk(uploadStat.chunks.length, batchSize)
        }
        return []
    })
    return uploadStat
}
